`timescale 1ns / 1ps

module tb_aru_reduce_sum_4_to_1;

    // FP32类型定义
    typedef struct packed {
        logic        sign;
        logic [7:0]  exponent;
        logic [22:0] mantissa;
    } fp32_t;

    // 时钟和复位
    logic  clk;
    logic  rst_n;
    logic  en;
    logic  psum_en;
    logic  end_reduce;
    logic  dat_in_vld  [3:0];
    fp32_t dat_in      [3:0];
    fp32_t dat_out;
    logic  dat_out_vld;

    // 实例化DUT
    aru_reduce_sum_4_to_1 dut (
        .clk(clk),
        .rst_n(rst_n),
        .en(en),
        .psum_en(psum_en),
        .end_reduce(end_reduce),
        .dat_in_vld(dat_in_vld),
        .dat_in(dat_in),
        .dat_out(dat_out),
        .dat_out_vld(dat_out_vld)
    );

    // 时钟生成
    initial begin
        clk = 0;
        forever #5 clk = ~clk;
    end

    // 手动实现的FP32转换函数
    function fp32_t real_to_fp32(real value);
        fp32_t result;
        real   abs_val;
        int    exp_val;
        real   mant_val;

        if (value < 0) begin
            result.sign = 1'b1;
            abs_val     = -value;
        end else begin
            result.sign = 1'b0;
            abs_val     = value;
        end

        if (abs_val == 0.0) begin
            result.exponent = 8'h00;
            result.mantissa = 23'h000000;
            return result;
        end

        exp_val  = 0;
        mant_val = abs_val;

        if (mant_val >= 2.0) begin
            while (mant_val >= 2.0 && exp_val < 128) begin
                mant_val = mant_val / 2.0;
                exp_val  = exp_val + 1;
            end
        end else if (mant_val < 1.0) begin
            while (mant_val < 1.0 && exp_val > -126) begin
                mant_val = mant_val * 2.0;
                exp_val  = exp_val - 1;
            end
        end

        if (exp_val > 127) begin
            result.exponent = 8'hFF;
            result.mantissa = 23'h000000;
            return result;
        end else if (exp_val < -126) begin
            result.exponent = 8'h00;
            result.mantissa = 23'h000000;
            return result;
        end

        result.exponent = exp_val + 127;
        mant_val        = mant_val - 1.0;
        mant_val        = mant_val * 8388608.0;
        result.mantissa = $rtoi(mant_val);

        return result;
    endfunction

    function real fp32_to_real(fp32_t fp32);
        logic [31:0] bits;
        real         result;

        bits   = {fp32.sign, fp32.exponent, fp32.mantissa};
        result = $bitstoshortreal(bits);

        return result;
    endfunction

    // 特殊值定义
    localparam fp32_t POS_INF_FP32 = '{sign: 1'b0, exponent: 8'hFF, mantissa: 23'h000000};
    localparam fp32_t NEG_INF_FP32 = '{sign: 1'b1, exponent: 8'hFF, mantissa: 23'h000000};
    localparam fp32_t NAN_FP32 = '{sign: 1'b0, exponent: 8'hFF, mantissa: 23'h400000};
    localparam fp32_t POS_ZERO_FP32 = '{sign: 1'b0, exponent: 8'h00, mantissa: 23'h000000};
    localparam fp32_t NEG_ZERO_FP32 = '{sign: 1'b1, exponent: 8'h00, mantissa: 23'h000000};

    // 期望值队列，用于输出检查
    typedef struct {
        real expected;
        string test_name;
        real tolerance;
        int timestamp;
        logic should_check;  // 是否需要检查（有些输出不需要检查）
    } expected_result_t;

    expected_result_t expected_queue      [$];
    int               input_timestamp = 0;

    // 发送数据任务（不等待）
    task send_data(input logic [0:3] vld, input real d0, d1, d2, d3, input logic psum, input logic end_red,
                   input string test_name = "", input real expected = 0.0, input real tolerance = 0.01);
        fp32_t            temp[4];
        expected_result_t exp;

        @(posedge clk);
        en            <= 1'b1;
        psum_en       <= psum;
        end_reduce    <= end_red;
        dat_in_vld[0] <= vld[0];
        dat_in_vld[1] <= vld[1];
        dat_in_vld[2] <= vld[2];
        dat_in_vld[3] <= vld[3];

        temp[0] = real_to_fp32(d0);
        temp[1] = real_to_fp32(d1);
        temp[2] = real_to_fp32(d2);
        temp[3] = real_to_fp32(d3);

        if (vld[0]) dat_in[0] <= temp[0];
        if (vld[1]) dat_in[1] <= temp[1];
        if (vld[2]) dat_in[2] <= temp[2];
        if (vld[3]) dat_in[3] <= temp[3];

        $display("[%0t] Send #%0d: %s vld=%04b psum=%b end=%b", $time, input_timestamp, test_name, vld, psum, end_red);
        if (vld[0]) $display("  [0]=%f", d0);
        if (vld[1]) $display("  [1]=%f", d1);
        if (vld[2]) $display("  [2]=%f", d2);
        if (vld[3]) $display("  [3]=%f", d3);

        // 每次发送都添加到期望队列
        // 只有end_reduce=1时需要检查
        exp.expected     = expected;
        exp.test_name    = test_name;
        exp.tolerance    = tolerance;
        exp.timestamp    = input_timestamp;
        exp.should_check = end_red;  // 只有end_reduce=1时才检查

        if (test_name != "" && end_red) begin
            expected_queue.push_back(exp);
            $display("  -> Added to queue (should_check=%0b, queue_size=%0d)", end_red, expected_queue.size());
        end

        input_timestamp++;
    endtask

    // 发送特殊值数据
    task send_special_data(input logic [3:0] vld, input fp32_t d0, d1, d2, d3, input logic psum, input logic end_red,
                           input string test_name = "");
        expected_result_t exp;

        @(posedge clk);
        en            <= 1'b1;
        psum_en       <= psum;
        end_reduce    <= end_red;
        dat_in_vld[0] <= vld[0];
        dat_in_vld[1] <= vld[1];
        dat_in_vld[2] <= vld[2];
        dat_in_vld[3] <= vld[3];
        if (vld[0]) dat_in[0] <= d0;
        if (vld[1]) dat_in[1] <= d1;
        if (vld[2]) dat_in[2] <= d2;
        if (vld[3]) dat_in[3] <= d3;

        $display("[%0t] Send Special #%0d: %s vld=%04b psum=%b end=%b", $time, input_timestamp, test_name, vld, psum,
                 end_red);

        // 特殊值测试也添加到队列
        exp.expected     = 0.0;  // 特殊值不检查数值
        exp.test_name    = test_name;
        exp.tolerance    = 0.0;
        exp.timestamp    = input_timestamp;
        exp.should_check = 0;  // 特殊值只显示，不做数值检查

        if (test_name != "") begin
            expected_queue.push_back(exp);
        end

        input_timestamp++;
    endtask

    // 输出监控进程
    int output_count = 0;
    always @(posedge clk) begin
        if (dat_out_vld) begin
            real              actual;
            expected_result_t exp;
            real              rel_error;

            actual = fp32_to_real(dat_out);

            $display("[%0t] === OUTPUT #%0d === 0x%08h = %f", $time, output_count, dat_out, actual);

            // 检查是否有期望值
            if (expected_queue.size() > 0) begin
                exp = expected_queue.pop_front();

                $display("  Checking against: %s (should_check=%0b)", exp.test_name, exp.should_check);

                if (exp.should_check) begin
                    if (exp.expected != 0.0) begin
                        rel_error = $abs((actual - exp.expected) / exp.expected);
                    end else begin
                        rel_error = $abs(actual - exp.expected);
                    end

                    if (exp.expected != exp.expected && actual != actual) begin
                        $display("  [PASS] %s: Both NaN", exp.test_name);
                    end else if (rel_error < exp.tolerance) begin
                        $display("  [PASS] %s: Expected=%f Got=%f Error=%f%%", exp.test_name, exp.expected, actual,
                                 rel_error * 100);
                    end else begin
                        $display("  [FAIL] %s: Expected=%f Got=%f Error=%f%%", exp.test_name, exp.expected, actual,
                                 rel_error * 100);
                    end
                end else begin
                    $display("  [INFO] %s: Output=%f (no check)", exp.test_name, actual);
                end
            end else begin
                $display("  [WARN] No expected value in queue");
            end

            output_count++;
            $display("====== OUTPUT END ======\n");
        end
    end

    // 主测试流程
    initial begin
        $display("========================================");
        $display("Starting FP32 Reduce Sum Testbench (Fast Mode)");
        $display("========================================");

        // 初始化
        rst_n      = 0;
        en         = 0;
        psum_en    = 0;
        end_reduce = 0;
        for (int i = 0; i < 4; i++) begin
            dat_in_vld[i] = 0;
            dat_in[i]     = '0;
        end

        #20;
        rst_n = 1;
        #10;

        // Test 1-5: 连续发送基本测试
        send_data(4'b1111, 1.0, 1.0, 1.0, 1.0, 1'b1, 1'b0, "PSUM-Test19 R1");
        send_data(4'b1111, 1.0, 1.0, 1.0, 1.0, 1'b1, 1'b0, "PSUM-Test19 R2");
        send_data(4'b1111, 1.0, 1.0, 1.0, 1.0, 1'b1, 1'b0, "PSUM-Test19 R3");
        send_data(4'b1111, 1.0, 1.0, 1.0, 1.0, 1'b1, 1'b0, "PSUM-Test19 R4");
        send_data(4'b1111, 1.0, 1.0, 1.0, 1.0, 1'b1, 1'b1, "Test19: PSUM-5Groups", 20.0);

        send_data(4'b1111, 0.0, 0.0, 0.0, 0.0, 1'b0, 1'b0, "PSUM-Test19 R1");

        $display("\n--- Basic Tests (Continuous) ---");
        send_data(4'b1111, 1.0, 2.0, 3.0, 4.0, 1'b0, 1'b1, "Test1: Basic Sum", 10.0);
        send_data(4'b1111, -1.0, -2.0, -3.0, -4.0, 1'b0, 1'b1, "Test2: Negative", -10.0);
        send_data(4'b1111, 5.0, -3.0, 2.0, -1.0, 1'b0, 1'b1, "Test3: Mixed", 3.0);
        send_data(4'b1100, 10.0, 20.0, 99.0, 99.0, 1'b0, 1'b1, "Test4: Partial[1100]", 30.0);
        send_data(4'b0011, 99.0, 99.0, 5.0, 15.0, 1'b0, 1'b1, "Test5: Partial[0011]", 20.0);

        // Test 6-10: 更多基本测试
        send_data(4'b1010, 7.0, 99.0, 3.0, 99.0, 1'b0, 1'b1, "Test6: Partial[1010]", 10.0);
        send_data(4'b1111, 0.0, 0.0, 0.0, 0.0, 1'b0, 1'b1, "Test7: All Zero", 0.0);
        send_data(4'b1111, 0.5, 0.25, 0.125, 0.0625, 1'b0, 1'b1, "Test8: Decimals", 0.9375, 0.001);
        send_data(4'b1111, 1000.0, 2000.0, 3000.0, 4000.0, 1'b0, 1'b1, "Test9: Large", 10000.0);
        send_data(4'b0000, 99.0, 99.0, 99.0, 99.0, 1'b0, 1'b1, "Test10: All Invalid", 0.0);

        // Test 11: PSUM测试（连续发送，最后一个end_reduce=1, psum_en=1）
        $display("\n--- PSUM Test (3 cycles) ---");
        send_data(4'b1111, 1.0, 2.0, 3.0, 4.0, 1'b1, 1'b0, "PSUM Round 1");
        send_data(4'b1111, 5.0, 6.0, 7.0, 8.0, 1'b1, 1'b0, "PSUM Round 2");
        send_data(4'b1111, 9.0, 10.0, 11.0, 12.0, 1'b1, 1'b1, "Test11: PSUM Total", 78.0);

        // Test 12: 另一个PSUM测试
        $display("\n--- PSUM Partial Valid Test ---");
        send_data(4'b1111, 10.0, 20.0, 30.0, 40.0, 1'b1, 1'b0, "PSUM2 R1");
        send_data(4'b1100, 50.0, 60.0, 0.0, 0.0, 1'b1, 1'b0, "PSUM2 R2");
        send_data(4'b0011, 0.0, 0.0, 70.0, 80.0, 1'b1, 1'b1, "Test12: PSUM Partial", 360.0);

        $display("\n--- PSUM Test: 8 Groups (32 numbers) ---");
        send_data(4'b1111, 1.0, 1.0, 1.0, 1.0, 1'b1, 1'b0, "PSUM-8G R1");
        send_data(4'b1111, 1.0, 1.0, 1.0, 1.0, 1'b1, 1'b0, "PSUM-8G R2");
        send_data(4'b1111, 1.0, 1.0, 1.0, 1.0, 1'b1, 1'b0, "PSUM-8G R3");
        send_data(4'b1111, 1.0, 1.0, 1.0, 1.0, 1'b1, 1'b0, "PSUM-8G R4");
        send_data(4'b1111, 1.0, 1.0, 1.0, 1.0, 1'b1, 1'b0, "PSUM-8G R5");
        send_data(4'b1111, 1.0, 1.0, 1.0, 1.0, 1'b1, 1'b0, "PSUM-8G R6");
        send_data(4'b1111, 1.0, 1.0, 1.0, 1.0, 1'b1, 1'b0, "PSUM-8G R7");
        send_data(4'b1111, 1.0, 1.0, 1.0, 1.0, 1'b1, 1'b1, "Test18: PSUM-8Groups", 32.0);

        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R1");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R2");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R3");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R4");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R5");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R6");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R7");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R8");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R9");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R10");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R11");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R12");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R13");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R14");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R15");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R16");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R17");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R18");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R19");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R20");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R21");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R22");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R23");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R24");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R25");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R26");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R27");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R28");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R29");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R30");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b0, "PSUM-32G R31");
        send_data(4'b1111, 1.0, 12.0, 33.0, 54.0, 1'b1, 1'b1, "Test1: PSUM-32Groups", 3200.0);

        // Test 13-15: 更多测试
        send_data(4'b1111, 10.0, -10.0, 5.0, -5.0, 1'b0, 1'b1, "Test13: Cancellation", 0.0);
        send_data(4'b1111, 1000.0, 0.001, 2000.0, 0.002, 1'b0, 1'b1, "Test14: Diff Magnitude", 3000.003, 0.01);
        send_data(4'b1111, 3.0, 3.0, 3.0, 3.0, 1'b0, 1'b1, "Test15: Same Values", 12.0);

        // Test 16: 特殊值测试
        $display("\n--- Special Values Test ---");
        send_special_data(4'b1111, POS_INF_FP32, real_to_fp32(5.0), real_to_fp32(3.0), real_to_fp32(2.0), 1'b0, 1'b1,
                          "Test16: +INF");
        send_special_data(4'b1111, NEG_INF_FP32, real_to_fp32(5.0), real_to_fp32(3.0), real_to_fp32(2.0), 1'b0, 1'b1,
                          "Test17: -INF");
        send_special_data(4'b1111, NAN_FP32, real_to_fp32(5.0), real_to_fp32(3.0), real_to_fp32(2.0), 1'b0, 1'b1,
                          "Test18: NaN");

        // 等待所有输出完成
        repeat (20) @(posedge clk);

        $display("\n========================================");
        $display("Testbench Completed");
        $display("Total outputs: %0d", output_count);
        $display("Remaining in queue: %0d", expected_queue.size());
        $display("========================================");
        $finish;
    end

    initial begin
        $fsdbDumpfile("./aru_reduce_sum_4_to_1.fsdb");
        $fsdbDumpvars("+all");
    end

    // 超时保护
    initial begin
        #50000;
        $display("ERROR: Testbench timeout!");
        $finish;
    end

endmodule
